<template>
    <div class="index">
        <div id="container"></div>
        <div ref="Label" class="tips">
            <div class="close" @click="closeChange">×</div>
            <div>{{ selectBoxName }}</div>
            <div>长：2mm</div>
            <div>宽：2mm</div>
            <div>高：2mm</div>
        </div>
    </div>
</template>

<script>
// 导入three
import * as THREE from 'three' 
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' 
// gltf加载器
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' 
// hdr加载器
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js' 
// 模型解压器
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js" 
// 引入tweenjs (注意版本，太高可能出错)
import  TWEEN  from "@tweenjs/tween.js" 

// 后期效果合成器
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' 
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js' 
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js' 
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js' 
import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js' 

// 标签渲染器
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'


export default {
    data() {
        this.scene = null  // 场景
        this.composer = null // 后期效果处理
        this.outlinePass = null // 外边框
        this.selectedObjects = [] // 描边效果模型组(写在data里面，会变成Proxy(Array))
        

        return {
            camera: null, // 相机
            renderer: null, // webgl渲染器
            clock: null, // 创建时钟
            cameraPos0: { x: -0.1, y: 6.25, z: 13.8 }, // 相机初始位置
            target0: { x: 0, y: 0, z: 0 }, // 相机初始看向位置
            mixer: null, // 声明动画混合器
            loader: null, // 创建GLTF加载器对象
            controls: null, // 轨道控制器

            selectBoxName: null, // 弹窗标题
            css2DRenderer: null, // 创建css2DRenderer
            label: null, // 弹窗

            // 鼠标点击前后位置
            beforeX: null, 
            beforeY: null, 
            afterX: null, 
            afterY: null
        }
    },
    mounted() {
        this.init()
    },
    methods: {
        init() {
            //TODO:创建时钟
            this.clock = new THREE.Clock()
            const el = document.getElementById('container')

            this.initScene() // 创建场景
            this.initCamera() // 创建相机
            this.initRenderer(el) // 创建webgl渲染器
            this.initGltf() // 创建GLTF加载器对象
            this.initCube() // 导入模型
            this.initOutlinePass() // 后期效果处理
            this.initLight() // 初始化灯光
            this.initRgbe() // 创建RGBE加载器对象
            this.initOrbitControls() // 创建轨道控制器
            this.createCameraTween(this.cameraPos0, this.target0, 3000) // 相机初始位置
            this.initCSS2DRenderer() // 标签渲染器
            this.initLabel() // 向场景中添加标签
            this.animate() // 渲染函数

            this.mouseMonitor() // 鼠标点击前后位置
            window.addEventListener('click', this.onMouseClick) // 监听窗口点击事件
            window.addEventListener('resize', this.onWindowResize) // 监听窗口变化
        },
        // 创建场景
        initScene() {
            this.scene = new THREE.Scene()
            this.scene.background = new THREE.Color('#000')
        },
        // 创建相机
        initCamera() {
            this.camera = new THREE.PerspectiveCamera(
                45, // 视角
                window.innerWidth / window.innerHeight, // 宽高比
                1, // 近平面
                1000 // 远平面
            )
            // 设置相机位置
            // this.camera.position.set(-0.1, 6.25, 13.8)
            // this.camera.lookAt(0, 0, 0)
        },
        // 创建webgl渲染器
        initRenderer(el) {
            this.renderer = new THREE.WebGLRenderer({
                antialias: true // 优化锯齿
            }) 
            this.renderer.setSize(window.innerWidth, window.innerHeight) 
            el.appendChild(this.renderer.domElement) 
        },
        // 创建GLTF加载器对象
        initGltf() {
            // 创建GLTF加载器对象
            this.loader = new GLTFLoader() 
            const dracoLoader = new DRACOLoader() 
            dracoLoader.setDecoderPath("draco/") 
        },
        // 导入模型
        initCube() {
            let group = new THREE.Group()
            this.loader.load('./model.glb', glb => {
                console.log('导入模型', glb) 
                group = glb.scene // blender模型
                this.scene.add(group)

                // 设置一个天空盒背景
                const geometry = new THREE.SphereGeometry(100) 
                const texture = new THREE.TextureLoader().load('./bj.jpg') // 加载天空盒贴图作为球体的颜色贴图
                texture.colorSpace = THREE.SRGBColorSpace  // 与webgl渲染器的outputColorSpace属性值一样
                const material = new THREE.MeshBasicMaterial({
                    side: THREE.BackSide, // 背面可见
                    map: texture, // 作为球体的颜色贴图
                }) 
                const mesh = new THREE.Mesh(geometry, material) 
                this.scene.add(mesh) 

                //TODO:激活动画
                this.mixer = new THREE.AnimationMixer(group)
                glb.animations.map((item, index) => {
                    this.mixer.clipAction(glb.animations[index]).play() 
                })
                this.animate()
            })
        },
        // 初始化灯光
        initLight() {
            const light = new THREE.AmbientLight(0xff8800, 0.3)
            this.scene.add(light)
        },
        // 创建RGBE加载器对象
        initRgbe() {
            const rgbeLoader = new RGBELoader() 
            rgbeLoader.load('./天空.hdr', envMap => {
                this.scene.environment = envMap 
                // hdr作为环境贴图生效
                envMap.mapping = THREE.EquirectangularReflectionMapping 
            })
        },
        // 创建轨道控制器
        initOrbitControls() {
            this.controls = new OrbitControls(this.camera, this.renderer.domElement)
            this.controls.minDistance = 1 // 相机距离观察目标最小距离
            this.controls.maxDistance = 30 // 相机距离观察目标最大距离
            // this.controls.enableZoom = false // 禁止缩放
            // this.controls.enablePan = false // 禁止平移
            this.controls.enableDamping = true // 设置带阻尼的惯性
            this.controls.dampingFactor = 0.05 // 设置阻尼系数
            // this.controls.autoRotate = true // 设置旋转
            // this.controls.autoRotateSpeed = 0.2  // 自动旋转的速度

            // 相机事件
            // this.controls.addEventListener('change', () => {
            //     console.log('当前相机坐标', this.camera.position) 
            // })
        },
        // 后期效果处理
        initOutlinePass() {
            this.composer = new EffectComposer(this.renderer) 
            const renderPass = new RenderPass(this.scene, this.camera)  // 渲染通道
            this.composer.addPass(renderPass)  // 将渲染通道加入效果器
            this.outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), this.scene, this.camera)  // 描边效果
            this.outlinePass.visibleEdgeColor.set('#ffff00')  // 发光颜色
            this.outlinePass.hiddenEdgeColor.set('gray')  // 被其他物体挡住部分的颜色
            this.outlinePass.edgeGlow = 0.0  // 边缘光辉大小
            this.outlinePass.usePatternTexture = false // 默认是false,改成true后仅显示边框
            this.outlinePass.edgeThickness = 3.0 // 发光厚度
            this.outlinePass.edgeStrength = 6.0 // 发光强度
            this.outlinePass.downSampleRatio = 2 // 能改变发光的宽度，建议默认不动
            this.outlinePass.pulsePeriod = 0 // 边缘闪烁，默认0无闪烁，数字越小闪烁越快
            this.composer.addPass(this.outlinePass) 
            // 不加的话外部模型没贴图
            const outputPass = new OutputPass() 
            this.composer.addPass(outputPass) 
            // 抗锯齿
            const smaaPass = new SMAAPass(window.innerWidth * this.renderer.getPixelRatio(), window.innerHeight * this.renderer.getPixelRatio())
            this.composer.addPass(smaaPass)
        },
        // 鼠标点击事件
        onMouseClick(event) {
            if (this.beforeX === this.afterX && this.beforeY === this.afterY) { // 避免拖动点击
                let materail = this.raycaster(event, this.scene.children)
                if (materail.length <= 0) {
                    console.log('未点击到物体')
                    return
                }
                const selectBoxObj = materail[0].object
                console.log('第一个交点对象', selectBoxObj) 
                this.outlinePass.selectedObjects = []
                this.selectedObjects = [] 
    
                this.selectedObjects.push(selectBoxObj)
                const name = selectBoxObj.name
                const A = this.scene.getObjectByName(name)
                const pos = new THREE.Vector3()
                // 获取三维场景中某个对象的坐标
                A.getWorldPosition(pos)
                // 向量的xyz坐标分别在pos基础上增加--
                const pos2 = pos.clone().addScalar(4)
                if (name === '货架') {
                    this.createCameraTween(pos2, pos)
                    this.outlinePass.selectedObjects = this.selectedObjects

                    this.selectBoxName = name
                    this.label.element.style.visibility = 'visible'
                    this.label.position.copy(selectBoxObj.position)
                } else if (this.label.element.style.visibility === 'visible') {
                    this.closeChange()
                }
            }
        },
        // 从摄像机发射一条射线,获得射线经过的对象
        raycaster(event, geometry) {
            // 声明变量（用于记录点击位置和射线）
            let x = (event.clientX / window.innerWidth) * 2 - 1
            let y = -(event.clientY / window.innerHeight) * 2 + 1
            let raycaster = new THREE.Raycaster()
            raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera)
            return raycaster.intersectObjects(geometry)
        },

        // 创建标签渲染器
        initCSS2DRenderer() {
            this.css2DRenderer = new CSS2DRenderer()
            this.css2DRenderer.setSize(window.innerWidth, window.innerHeight) // 设置大小，与容器相同
            this.css2DRenderer.domElement.style.position = 'absolute' // 绝对定位
            this.css2DRenderer.domElement.style.top = '-150px'
            this.css2DRenderer.domElement.style.left = '0px'
            this.css2DRenderer.domElement.style.pointerEvents = 'none' // 避免影响模型的点击事件 none不响应 auto响应
            document.body.appendChild(this.css2DRenderer.domElement) // 添加至容器
        },
        // 标签内容
        getCSS2DObject(domEle) {
            let label = new CSS2DObject(domEle)
            domEle.style.pointerEvents = 'none'
            return label
        },
        // 向场景中添加标签
        initLabel() {
            let labelDomEle = this.$refs.Label
            labelDomEle.style.visibility = 'hidden'
            // labelDomEle.style.pointerEvents = 'auto'
            this.label = this.getCSS2DObject(labelDomEle)
            this.scene.add(this.label)
        },
        // 关闭标签
        closeChange() {
            this.outlinePass.selectedObjects = []; // 无发光描边
            this.label.element.style.visibility = 'hidden' // 从场景移除
            this.createCameraTween(this.cameraPos0, this.target0)
        },

        // 相机动画函数，从A到B飞行，A点表示相机当前所处状态
        // pos: 三维向量Vector3，表示动画结束相机位置
        // target: 三维向量Vector3，表示相机动画结束lookAt指向的目标观察点
        createCameraTween(endPos, endTarget, time) {
            new TWEEN.Tween({
                // 读取当前位置和目标观察点
                x: this.camera.position.x,
                y: this.camera.position.y,
                z: this.camera.position.z,
                tx: this.controls.target.x,
                ty: this.controls.target.y,
                tz: this.controls.target.z,
            })
                .to({
                    // 结束相机位置
                    x: endPos.x,
                    y: endPos.y,
                    z: endPos.z,
                    // 动画结束相机指向的目标观察点
                    tx: endTarget.x,
                    ty: endTarget.y,
                    tz: endTarget.z,
                }, time)
                // 每次更新时执行
                .onUpdate( obj => {
                    this.camera.position.set(obj.x, obj.y, obj.z) // 动态改变相机位置
                    this.camera.lookAt(obj.tx, obj.ty, obj.tz) // 动态计算相机视线
                    this.controls.target.set(obj.tx, obj.ty, obj.tz) // 锁定相机观察视角
                })
                // 补间动画完成时执行
                .onComplete( obj => {
                    this.controls.update()
                })
                .start()

        },

        // 渲染函数
        animate() {
            TWEEN.update()
            this.controls.update() 
            requestAnimationFrame(this.animate) 
            //TODO:更新模型动画
            if (this.mixer) {
                const delta = this.clock.getDelta() 
                this.mixer.update(delta) 
            }
            // 刷新标签渲染
            this.css2DRenderer.render(this.scene, this.camera) 
            // 刷新渲染器（必须打开，后期处理器才有效果）
            this.composer.render() // 刷新后期渲染器
            // this.renderer.render(this.scene, this.camera) 
        },

        // 鼠标抬起、落下是否同一位置监测 
        mouseMonitor() {
            window.addEventListener('mousedown', event => {
                [this.beforeX, this.beforeY] = [event.offsetX, event.offsetY]
            })
            window.addEventListener('mouseup', event => {
                [this.afterX, this.afterY] = [event.offsetX, event.offsetY]
            })
        },
        // 监听窗口变化
        onWindowResize() {
            this.camera.aspect = window.innerWidth / window.innerHeight // 重置相机宽高比
            this.camera.updateProjectionMatrix() // 更新相机投影矩阵
            this.renderer.setSize(window.innerWidth, window.innerHeight) // 重置渲染器宽高比
        },

        
    }
}

</script>

<style lang="scss">
$main: rgb(123, 30, 231, 0.2);
.tips{
    visibility: hidden;
    width: 200px;
    height: 100px;
    border: 1px solid #fff;
    padding: 24px 12px 24px 12px;
    /* border-radius: 5px; */
    background-color: $main;
    /* box-shadow: 0 0 5px 2px blue inset; */
    /* opacity: 0.8; */
    color: #fff;
    div{
        margin-bottom: 4px;
    }
}
.close{
    position: absolute;
    top: 8px;
    right: 8px;
    font-size: 24px;
}
</style>